using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class ShapePath
    {
        private class ShapePoints
        {
            public Shape s;
            public ListEx<Vector2> p;
        }
        private class ShapeHoles
        {
            public Path h;
            public Vector2 p;
        }
        #region Properties

        public string type;
        public Color color;
        public ListEx<Path> subPaths;
        public Path currentPath;

        #endregion

        #region constructor
        public ShapePath()
        {
            this.type = "ShapePath";
            this.color = new Color();
            this.subPaths = new ListEx<Path>();
            this.currentPath = null;
        }
        #endregion

        #region methods
        public ShapePath moveTo(double x, double y)
        {
            this.currentPath = new Path();
            this.subPaths.Push(this.currentPath);
            this.currentPath.moveTo(x, y);
            return this;
        }
        public ShapePath lineTo(double x, double y)
        {
            this.currentPath.lineTo(x, y);
            return this;
        }
        public ShapePath quadraticCurveTo(double aCPx, double aCPy, double aX, double aY)
        {
            this.currentPath.quadraticCurveTo(aCPx, aCPy, aX, aY);
            return this;
        }
        public ShapePath bezierCurveTo(double aCP1x, double aCP1y, double aCP2x, double aCP2y, double aX, double aY)
        {
            this.currentPath.bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY);
            return this;
        }
        public ShapePath splineThru(ListEx<Vector2> pts)
        {
            this.currentPath.splineThru(pts);
            return this;
        }
        public ListEx<Shape> toShapesNoHoles(ListEx<Path> inSubpaths)
        {
            var shapes = new ListEx<Shape>();
            for (int i = 0, l = inSubpaths.Length; i < l; i++)
            {
                var tmpPath = inSubpaths[i];
                var tmpShape = new Shape();
                tmpShape.curves = tmpPath.curves;
                shapes.Push(tmpShape);
            }
            return shapes;
        }
        public bool isPointInsidePolygon(Vector2 inPt, ListEx<Vector2> inPolygon)
        {
            var polyLen = inPolygon.Length;
            // inPt on polygon contour => immediate success    or
            // toggling of inside/outside at every single! intersection point of an edge
            //  with the horizontal line through inPt, left of inPt
            //  not counting lowerY endpoints of edges and whole edges on that line
            var inside = false;
            for (int p = polyLen - 1, q = 0; q < polyLen; p = q++)
            {
                var edgeLowPt = inPolygon[p];
                var edgeHighPt = inPolygon[q];
                var edgeDx = edgeHighPt.X - edgeLowPt.X;
                var edgeDy = edgeHighPt.Y - edgeLowPt.Y;
                if (Math.Abs(edgeDy) > MathEx.EPSILON)
                {
                    // not parallel
                    if (edgeDy < 0)
                    {
                        edgeLowPt = inPolygon[q]; edgeDx = -edgeDx;
                        edgeHighPt = inPolygon[p]; edgeDy = -edgeDy;
                    }
                    if ((inPt.Y < edgeLowPt.Y) || (inPt.Y > edgeHighPt.Y)) continue;
                    if (inPt.Y == edgeLowPt.Y)
                    {
                        if (inPt.X == edgeLowPt.X) return true;     // inPt is on contour ?
                                                                    // continue;				// no intersection or edgeLowPt => doesn"t count !!!
                    }
                    else
                    {
                        var perpEdge = edgeDy * (inPt.X - edgeLowPt.X) - edgeDx * (inPt.Y - edgeLowPt.Y);
                        if (perpEdge == 0) return true;     // inPt is on contour ?
                        if (perpEdge < 0) continue;
                        inside = !inside;       // true intersection left of inPt
                    }
                }
                else
                {
                    // parallel or collinear
                    if (inPt.Y != edgeLowPt.Y) continue;           // parallel
                                                                   // edge lies on the same horizontal line as inPt
                    if (((edgeHighPt.X <= inPt.X) && (inPt.X <= edgeLowPt.X)) ||
                         ((edgeLowPt.X <= inPt.X) && (inPt.X <= edgeHighPt.X))) return true;    // inPt: Point on contour !
                                                                                                // continue;
                }
            }
            return inside;
        }
        public ListEx<Shape> toShapes(bool isCCW = false, bool noHoles = false)
        {
            var subPaths = this.subPaths;
            if (subPaths.Length == 0) return new ListEx<Shape>();

            if (noHoles) return toShapesNoHoles(subPaths);

            bool solid;
            Path tmpPath;
            Shape tmpShape;
            var shapes = new ListEx<Shape>();
            if (subPaths.Length == 1)
            {
                tmpPath = subPaths[0];
                tmpShape = new Shape();
                tmpShape.curves = tmpPath.curves;
                shapes.Push(tmpShape);
                return shapes;
            }
            var holesFirst = !ShapeUtils.isClockWise(subPaths[0].getPoints());
            holesFirst = isCCW ? !holesFirst : holesFirst;
            // console.log("Holes first", holesFirst);
            var betterShapeHoles = new ListEx<ListEx<ShapeHoles>>();
            var newShapes = new ListEx<ShapePoints>();
            var newShapeHoles = new ListEx<ListEx<ShapeHoles>>();
            var mainIdx = 0;
            var tmpPoints = new ListEx<Vector2>();

            newShapeHoles.Push(new ListEx<ShapeHoles>());

            for (int i = 0, l = subPaths.Length; i < l; i++)
            {
                tmpPath = subPaths[i];
                tmpPoints = tmpPath.getPoints();
                solid = ShapeUtils.isClockWise(tmpPoints);
                solid = isCCW ? !solid : solid;
                if (solid)
                {
                    if ((!holesFirst) && (newShapes[mainIdx]) != null) mainIdx++;
                    newShapes.Push(new ShapePoints() { s = new Shape(), p = tmpPoints });
                    newShapes[mainIdx].s.curves = tmpPath.curves;
                    if (holesFirst) mainIdx++;
                    newShapeHoles[mainIdx] = new ListEx<ShapeHoles>();
                    //console.log("cw", i);
                }
                else
                {
                    newShapeHoles[mainIdx].Push(new ShapeHoles() { h = tmpPath, p = tmpPoints[0] });
                    //console.log("ccw", i);
                }
            }
            // only Holes? -> probably all Shapes with wrong orientation
            if (newShapes[0] == null) return toShapesNoHoles(subPaths);

            if (newShapes.Length > 1)
            {
                var ambiguous = false;
                var toChange = 0;
                for (int sIdx = 0, sLen = newShapes.Length; sIdx < sLen; sIdx++)
                {
                    betterShapeHoles.Push(new ListEx<ShapeHoles>());
                }
                for (int sIdx = 0, sLen = newShapes.Length; sIdx < sLen; sIdx++)
                {
                    var sho = newShapeHoles[sIdx];
                    for (int hIdx = 0; hIdx < sho.Length; hIdx++)
                    {
                        var ho = sho[hIdx];
                        var hole_unassigned = true;
                        for (int s2Idx = 0; s2Idx < newShapes.Length; s2Idx++)
                        {
                            if (isPointInsidePolygon(ho.p, newShapes[s2Idx].p))
                            {
                                if (sIdx != s2Idx) toChange++;
                                if (hole_unassigned)
                                {
                                    hole_unassigned = false;
                                    betterShapeHoles[s2Idx].Push(ho);
                                }
                                else
                                {
                                    ambiguous = true;
                                }
                            }
                        }
                        if (hole_unassigned)
                        {
                            betterShapeHoles[sIdx].Push(ho);
                        }
                    }
                }
                if (toChange > 0 && ambiguous == false)
                {
                    newShapeHoles = betterShapeHoles;
                }
            }
            ListEx<ShapeHoles> tmpHoles;
            for (int i = 0, il = newShapes.Length; i < il; i++)
            {
                tmpShape = newShapes[i].s;
                shapes.Push(tmpShape);
                tmpHoles = newShapeHoles[i];
                for (int j = 0, jl = tmpHoles.Length; j < jl; j++)
                {
                    tmpShape.holes.Push(tmpHoles[j].h);
                }
            }
            //console.log("shape", shapes);
            return shapes;
        }
        #endregion

    }
}
